package com.fafu.mq.mqserver;

import com.fafu.mq.common.Consumer;
import com.fafu.mq.common.MqException;
import com.fafu.mq.mqserver.core.*;
import com.fafu.mq.mqserver.datacenter.DiskDataCenter;
import com.fafu.mq.mqserver.datacenter.MemoryDataCenter;
import jakarta.websocket.RemoteEndpoint;
import lombok.Data;

import javax.swing.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 通过这个类, 来表示虚拟主机
 * 每个虚拟主机下都管理着自己的交换机, 队列, 绑定, 消息, 数据文件
 * 提供 API 给上层调用
 * 作为业务逻辑的整合者, 需要对代码中抛出的异常进行处理
 */

@Data
public class VirtualHost {
    private String virtualHostName;
    private MemoryDataCenter memoryDataCenter = new MemoryDataCenter();
    private DiskDataCenter diskDataCenter = new DiskDataCenter();
    private Router router = new Router();

    private ConsumerManager consumerManager = new ConsumerManager(this);
    private final Object exchangeLocker = new Object();
    private final Object queueLocker = new Object();

    public VirtualHost(String name){
        this.virtualHostName = name;

        // 对于 MemoryDataCenter, 不需要额外的初始化, new 出对象就可以了
        // 对于 DiskDataCenter , 需要进行初始化操作, 建库建表和初始化的设定
        diskDataCenter.init();

        // 另外需要对银盘中的数据尽心恢复到内存中
        try {
            memoryDataCenter.recovery(diskDataCenter);
        } catch (IOException | ClassNotFoundException | MqException e) {
            e.printStackTrace();
            System.out.println("[VirtualHost] 恢复内存数据失败");
        }
    }

    // 创建交换机
    // 如果交换机不存在就创建, 如果存在就直接返回
    // 创建成功返回 true, 创建失败放回 false
    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable,
                                   boolean autoDelete, Map<String,Object> arguments) {
        // 把交换机的名称, 加上虚拟主机作为前缀
        exchangeName = virtualHostName + exchangeName;
        try {
           synchronized (exchangeLocker){
               // 1. 判断交换机是否存在
               Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);
               if(existsExchange != null){
                   // 该交换机已存在
                   System.out.println("[VirtualHost] 交换机已经存在? exchangeName = "  + exchangeName);
                   return true;
               }
               // 2. 真正创建交换机, 先构建交换机对象
               Exchange exchange = new Exchange();
               exchange.setName(exchangeName);
               exchange.setType(exchangeType);
               exchange.setDurable(durable);
               exchange.setAutoDelete(autoDelete);
               exchange.setArguments(arguments);

               // 3. 把交换机写入硬盘
               if (durable){
                   diskDataCenter.insertExchange(exchange);
               }
               // 4. 把交换机写入内存
               memoryDataCenter.insertExchange(exchange);
               System.out.println("[VirtualHost] 交换机创建完成!  exchangeName = "  + exchangeName);
               // 上述逻辑先写硬盘再写内存, 目的是硬盘更容易写失败, 如果硬盘先写失败, 内存就不用写了
               // 要是内存先写, 内存写成功了, 硬盘失败了, 内存还要再删除一遍, 比较麻烦
           }
            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost] 交换机创建失败!  exchangeName = "  + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

    // 删除交换机
    public boolean exchangeDelete(String exchangeName){
        // 把交换机的名称, 加上虚拟主机作为前缀
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker){
                // 1. 找到对应的交换机
                Exchange toDelete = memoryDataCenter.getExchange(exchangeName);
                if(toDelete == null){
                    // 该交换机不存在
                    throw new MqException("[VirtualHost] 交换机不存在无法删除! ");
                }
                // 2. 删除硬盘的数据
                if (toDelete.isDurable()){
                    diskDataCenter.deleteExchange(exchangeName);
                }
                // 3. 删除内存中的交换机数据
                memoryDataCenter.deleteExchange(exchangeName);
                System.out.println("[VirtualHost] 交换机删除成功!  exchangeName = "  + exchangeName);
            }
            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost] 交换机删除失败!  exchangeName = "  + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

    // 创建队列
    public boolean queueDeclare(String queueName, boolean durable, boolean exclusive, boolean autoDelete,
                                Map<String,Object> arguments){
        // 把队列名字拼接上虚拟主机
        queueName = virtualHostName + queueName;
        try {
            synchronized (queueLocker){
                // 1. 判断队列是否存在
                MSGQueue existsQueue = memoryDataCenter.getQueue(queueName);
                if (existsQueue != null){
                    System.out.println("[VirtualHost] 队列已存在!  queueName = "  + queueName);
                    return true;
                }
                // 2. 创建队列对象
                MSGQueue queue = new MSGQueue();
                queue.setName(queueName);
                queue.setDurable(durable);
                queue.setExclusive(exclusive);
                queue.setAutoDelete(autoDelete);
                queue.setArguments(arguments);

                // 硬盘中写入数据文件
                if (durable){
                    diskDataCenter.insertQueue(queue);
                }
                // 4. 写内存
                memoryDataCenter.insertQueue(queue);
                System.out.println("[VirtualHost] 队列创建成功!  queueName = "  + queueName);
            }
            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost] 队列创建失败!  queueName = "  + queueName);
            e.printStackTrace();
            return false;
        }
    }

    public boolean queueDelete(String queueName){
        queueName = virtualHostName + queueName;
        try {
            synchronized (queueLocker){
                // 1. 根据队列名字, 查询队列对象是否存在
                MSGQueue queue = memoryDataCenter.getQueue(queueName);
                if (queue == null){
                    throw new MqException("[VirtualHost] 队列不存在无法删除! ");
                }
                // 2. 删除硬盘数据
                if (queue.isDurable()){
                    diskDataCenter.deleteQueue(queueName);
                }
                // 3. 删除内存数据
                memoryDataCenter.deleteQueue(queueName);
                System.out.println("[VirtualHost] 队列删除成功!  queueName = "  + queueName);
            }
            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost] 队列删除失败!  queueName = "  + queueName);
            e.printStackTrace();
            return false;
        }
    }

    // 绑定的创建
    public boolean queueBind(String queueName,String exchangeName, String bindingKey){
        queueName = virtualHostName + queueName;
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker){
                synchronized (queueLocker){
                    // 1. 判断绑定是否存在
                    Binding existsBinding = memoryDataCenter.getBinding(exchangeName,queueName);
                    if (existsBinding != null){
                        throw new MqException("[VirtualHost] 绑定已经存在! queueName = " + queueName +
                                " exchangeName = " + exchangeName);
                    }
                    // 2. 判断 bindingKey 是否合法
                    if(!router.checkBindingKey(bindingKey)){
                        throw new MqException("[VirtualHost] bindingKey不合法! bindingKey = " + bindingKey);
                    }

                    // 3. 创建绑定
                    Binding binding = new Binding();
                    binding.setQueueName(queueName);
                    binding.setExchangeName(exchangeName);
                    binding.setBindingKey(bindingKey);

                    // 4. 获取一下交换机和队列是否存在, 如果不存在的话也是无法进行绑定的
                    Exchange exchange = memoryDataCenter.getExchange(exchangeName);
                    if (exchange == null){
                        throw new MqException("[VirtualHost] 交换机不存在! exchangeName = " + exchangeName);
                    }
                    MSGQueue queue = memoryDataCenter.getQueue(queueName);
                    if(queue == null){
                        throw new MqException("[VirtualHost] 队列不存在! queueName = " + queueName);
                    }
                    // 5. 写入硬盘中
                    if (exchange.isDurable() && queue.isDurable()){
                        diskDataCenter.insertBinding(binding);
                    }
                    // 6. 写入内存中
                    memoryDataCenter.insertBinding(binding);
                    System.out.println("[VirtualHost] 绑定创建成功! queueName = " + queueName +
                            " exchangeName = " + exchangeName);
                }
                }

            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost] 绑定创建失败! queueName = " + queueName +
                    " exchangeName = " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

    // 解除绑定
    public boolean queueUnBind(String queueName,String exchangeName){
        queueName = virtualHostName + queueName;
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker){
                synchronized (queueLocker){
                    // 1. 获取 binding 看一下是否存在
                    Binding binding = memoryDataCenter.getBinding(exchangeName,queueName);
                    if (binding == null){
                        throw new MqException("[VirtualHost] 删除绑定失败, 绑定不存在! queueName = " + queueName +
                                " exchangeName = " + exchangeName);
                    }
//            // 2. 获取一下队列和交换机是否存在
//            Exchange exchange = memoryDataCenter.getExchange(exchangeName);
//            if (exchange == null){
//                throw new MqException("[VirtualHost] 对应的交换机不存在! exchangeName = " + exchangeName);
//            }
//            MSGQueue queue = memoryDataCenter.getQueue(queueName);
//            if(queue == null){
//                throw new MqException("[VirtualHost] 对应的队列不存在! queueName = " + queueName);
//            }

                    // 3. 删除硬盘上的数据
                    diskDataCenter.deleteBinding(binding);
                    // 4. 删除内存上的数据
                    memoryDataCenter.deleteBinding(binding);
                    System.out.println("删除绑定成功");
                }
            }
            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost] 解除绑定失败! ");
            e.printStackTrace();
            return false;
        }
    }

    // 发送消息到指定的交换机/队列中
    public boolean basicPublish(String exchangeName, String routingKey, BasicProperties basicProperties,byte[] body){
        try {
            // 1. 转换交换机的名称
            exchangeName = virtualHostName + exchangeName;
            // 2. 检查 routingKey 是否合法
            if (!router.checkRoutingKey(routingKey)){
                throw new MqException("[VirtualHost] routingKey不合法! routingKey = " + routingKey);
            }
            // 3. 查找交换机对象
            Exchange exchange = memoryDataCenter.getExchange(exchangeName);
            if (exchange == null){
                throw new MqException("[VirtualHost] 交换机不存在! exchangeName = " + exchangeName);
            }
            // 4. 判定交换机的类型
            if (exchange.getType() == ExchangeType.DIRECT){
                // 按照直接交换机的方式来转发消息
                // 以 routingKey 作为队列的名字, 直接把消息写入到队列中
                // 可以无视绑定关系
                String queueName = virtualHostName + routingKey;
                // 5. 构造消息对象
                Message message = Message.createMessageWithId(basicProperties,body,routingKey);
                // 6. 查找该队列名的对象
                MSGQueue queue = memoryDataCenter.getQueue(queueName);
                if(queue == null) {
                    throw new MqException("[VirtualHost] 对应的队列不存在! queueName = " + queueName);
                }
                // 7. 队列存在直接写入消息
                sendMessage(queue,message);
            } else {
                // 按照 fanout 和 topic 的方式来进行转发
                // 找到该交换机关联的所有绑定, 并遍历这些绑定对象
                ConcurrentHashMap<String,Binding> bindingsMap = memoryDataCenter.getBindings(exchangeName);
                for (Map.Entry<String,Binding> entry: bindingsMap.entrySet()){
                    // 1. 获取绑定对象
                    Binding binding = entry.getValue();
                    MSGQueue queue = memoryDataCenter.getQueue(binding.getQueueName());
                    if (queue == null){
                        // 此处可能会有很多个队列, 不要因为一个队列失败从而影响其他队列的传输
                        // 我们此处就不进行抛出异常
                        System.out.println("[VirtualHost] 发送消息时, 发现队列不存在! queueName = " + binding.getQueueName());
                        continue;
                    }
                    // 2. 构造消息
                    Message message = Message.createMessageWithId(basicProperties,body,routingKey);
                    // 判断消息是否能转发给该队列
                    // 如果是 fanout, 所有绑定的队列都要转发
                    // 如果是 topic, 还需要判定下, bindingKey 和 routingKey 是不是匹配关系
                    if (!router.route(exchange.getType(),binding,message)){
                        continue;
                    }
                    // 4. 真正转发消息给队列
                    sendMessage(queue,message);
                }
            }
            return true;
        }catch (Exception e){
            System.out.println("消息发送失败");
            e.printStackTrace();
            return false;
        }
    }

    public void sendMessage(MSGQueue queue, Message message) throws IOException, MqException, InterruptedException {
        // 此处发送消息就是写入 硬盘 和 内存
        int deliverMode = message.getDeliverMode();
        // 1 为不持久化, 2 为持久化
        if (deliverMode == 2){
            diskDataCenter.sendMessage(queue,message);
        }
        // 写入内存中
        memoryDataCenter.sendMessage(queue,message);

        // 此处还需要补充一个逻辑, 通知消费者可以取消息了
        consumerManager.notifyConsume(queue.getName());
    }

    // 订阅消息
    // 添加一个队列的订阅者, 当队列收到消息之后, 就把消息推给对应的订阅者
    // consumerTag: 消费者的标识
    // autoAck: 消息被消费完成以后, 应答方式为 true 自动应答, false 为手动应答
    // consumer: 是一个回调函数. 此处是一个函数式接口, 这样后续调用 basicConsume 传实参, 就可以写成 lambda 的样子
    public boolean basicConsume(String consumerTag, String queueName, boolean autoAck, Consumer consumer){
        queueName = virtualHostName + queueName;
        try {
            consumerManager.addConsumer(consumerTag,queueName,autoAck,consumer);
            System.out.println("[VirtualHost] basicConsume 成功! queueName = " + queueName);
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] basicConsume 失败! queueName = " + queueName);
            e.printStackTrace();
            return false;
        }
    }

    public boolean basicAck(String queueName, String messageId) {
        try {
            queueName = virtualHostName + queueName;
            // 1. 获取队列和消息
            MSGQueue queue = memoryDataCenter.getQueue(queueName);
            if (queue == null) {
                throw new MqException("[VirtualHost] 要确认的队列不存在! queueName = " + queueName);
            }
            Message message = memoryDataCenter.getMessageById(messageId);
            if (message == null) {
                throw new MqException("[VirtualHost] 要确认的队列不存在! messageId = " + messageId);
            }
            // 2. 删除硬盘的数据
            if (message.getDeliverMode() == 2) {
                diskDataCenter.deleteMessage(queue,message);
            }
            // 3. 删除内存的数据
            memoryDataCenter.deleteMessage(messageId);
            // 4. 删除待确认队列的消息
            memoryDataCenter.deleteMessageWaitAck(queueName,messageId);
            System.out.println("[VirtualHost] basicAck 成功! 消息确认成功 queueName = " + queueName
            + " messageId = " + messageId);
            return true;
        } catch (Exception e) {
            System.out.println("[VirtualHost] basicAck 失败! 消息确认失败 queueName = " + queueName
                    + " messageId = " + messageId);
            e.printStackTrace();
            return false;
        }
    }
}
